<?php

abstract class Jf_Tree_Node extends Jf_Tree_Container {

    protected $_label;

    protected $_fragment;

    protected $_id;

    protected $_class;

    protected $_title;

    protected $_target;

    protected $_accesskey;

    protected $_rel = array();

    protected $_rev = array();

    protected $_order;

    protected $_resource;

    protected $_privilege;

    protected $_active = false;

    protected $_visible = true;

    protected $_parent;

    protected $_properties = array();

    protected $_customHtmlAttribs = array();

    protected static $_defaultNodeType;

    public function __construct ($options = null, $adapter = null) {
        if (isset ($adapter)) {
            $this -> setAdapter ($adapter);
        }
        // do custom initialization
        $this -> _init ();
        if (is_array ($options)) {
            $this -> setOptions ($options);
        } elseif ($options instanceof Zend_Config) {
            $this -> setConfig ($options);
        }
    }

    protected function _init () {}

    public function setConfig (Zend_Config $config) {
        return $this -> setOptions ($config -> toArray ());
    }

    public function setOptions (array $options) {
        foreach ($options as $key => $value) {
            $this -> set ($key, $value);
        }
        return $this;
    }

    public function setLabel ($label) {
        if (null !== $label &&  ! is_string ($label)) {
            throw new Jf_Tree_Exception ('Invalid argument: $label must be a string or null');
        }
        $this -> _label = $label;
        return $this;
    }

    public function getLabel () {
        return $this -> _label;
    }

    public function setFragment ($fragment) {
        if (null !== $fragment &&  ! is_string ($fragment)) {
            throw new Jf_Tree_Exception ('Invalid argument: $fragment must be a string or null');
        }
        $this -> _fragment = $fragment;
        return $this;
    }

    public function getFragment () {
        return $this -> _fragment;
    }

    public function setId ($id = null) {
        if (null !== $id &&  ! is_string ($id) &&  ! is_numeric ($id)) {
            throw new Jf_Tree_Exception ('Invalid argument: $id must be a string, number or null');
        }
        $this -> _id = null === $id ? $id : (string) $id;
        return $this;
    }

    public function getId () {
        return $this -> _id;
    }

    public function setClass ($class = null) {
        if (null !== $class &&  ! is_string ($class)) {
            throw new Jf_Tree_Exception ('Invalid argument: $class must be a string or null');
        }
        $this -> _class = $class;
        return $this;
    }

    public function getClass () {
        return $this -> _class;
    }

    public function setTitle ($title = null) {
        if (null !== $title &&  ! is_string ($title)) {
            throw new Jf_Tree_Exception ('Invalid argument: $title must be a non-empty string');
        }
        $this -> _title = $title;
        return $this;
    }

    public function getTitle () {
        return $this -> _title;
    }

    public function setTarget ($target = null) {
        if (null !== $target &&  ! is_string ($target)) {
            throw new Jf_Tree_Exception ('Invalid argument: $target must be a string or null');
        }
        $this -> _target = $target;
        return $this;
    }

    public function getTarget () {
        return $this -> _target;
    }

    public function setAccesskey ($character = null) {
        if (null !== $character && ( ! is_string ($character) || 1 != strlen ($character))) {
            throw new Jf_Tree_Exception ('Invalid argument: $character must be a single character or null');
        }
        $this -> _accesskey = $character;
        return $this;
    }

    public function getAccesskey () {
        return $this -> _accesskey;
    }

    public function setRel ($relations = null) {
        $this -> _rel = array();
        if (null !== $relations) {
            if ($relations instanceof Zend_Config) {
                $relations = $relations -> toArray ();
            }
            if ( ! is_array ($relations)) {
                throw new Jf_Tree_Exception ('Invalid argument: $relations must be an ' . 'array or an instance of Zend_Config');
            }
            foreach ($relations as $name => $relation) {
                if (is_string ($name)) {
                    $this -> _rel[$name] = $relation;
                }
            }
        }
        return $this;
    }

    public function getRel ($relation = null) {
        if (null !== $relation) {
            return isset ($this -> _rel[$relation]) ? $this -> _rel[$relation] : null;
        }
        return $this -> _rel;
    }

    public function setRev ($relations = null) {
        $this -> _rev = array();
        if (null !== $relations) {
            if ($relations instanceof Zend_Config) {
                $relations = $relations -> toArray ();
            }
            if ( ! is_array ($relations)) {
                throw new Jf_Tree_Exception ('Invalid argument: $relations must be an ' . 'array or an instance of Zend_Config');
            }
            foreach ($relations as $name => $relation) {
                if (is_string ($name)) {
                    $this -> _rev[$name] = $relation;
                }
            }
        }
        return $this;
    }

    public function getRev ($relation = null) {
        if (null !== $relation) {
            return isset ($this -> _rev[$relation]) ? $this -> _rev[$relation] : null;
        }
        return $this -> _rev;
    }

    public function setCustomHtmlAttrib ($name, $value) {
        if ( ! is_string ($name)) {
            throw new Jf_Tree_Exception ('Invalid argument: $name must be a string');
        }
        if (null !== $value &&  ! is_string ($value)) {
            throw new Jf_Tree_Exception ('Invalid argument: $value must be a string or null');
        }
        if (null === $value && isset ($this -> _customHtmlAttribs[$name])) {
            unset ($this -> _customHtmlAttribs[$name]);
        } else {
            $this -> _customHtmlAttribs[$name] = $value;
        }
        return $this;
    }

    public function getCustomHtmlAttrib ($name) {
        if ( ! is_string ($name)) {
            throw new Jf_Tree_Exception ('Invalid argument: $name must be a string');
        }
        if (isset ($this -> _customHtmlAttribs[$name])) {
            return $this -> _customHtmlAttribs[$name];
        }
        return null;
    }

    public function setCustomHtmlAttribs (array $attribs) {
        foreach ($attribs as $key => $value) {
            $this -> setCustomHtmlAttrib ($key, $value);
        }
        return $this;
    }

    public function getCustomHtmlAttribs () {
        return $this -> _customHtmlAttribs;
    }

    public function removeCustomHtmlAttrib ($name) {
        if ( ! is_string ($name)) {
            throw new Jf_Tree_Exception ('Invalid argument: $name must be a string');
        }
        if (isset ($this -> _customHtmlAttribs[$name])) {
            unset ($this -> _customHtmlAttribs[$name]);
        }
    }

    public function clearCustomHtmlAttribs () {
        $this -> _customHtmlAttribs = array();
        return $this;
    }

    public function setOrder ($order = null) {
        if (is_string ($order)) {
            $temp = (int) $order;
            if ($temp < 0 || $temp > 0 || $order == '0') {
                $order = $temp;
            }
        }
        if (null !== $order &&  ! is_int ($order)) {
            throw new Jf_Tree_Exception ('Invalid argument: $order must be an integer or null, ' . 'or a string that casts to an integer');
        }
        $this -> _order = $order;
        // notify parent, if any
        if (isset ($this -> _parent)) {
            $this -> _parent -> notifyOrderUpdated ();
        }
        return $this;
    }

    public function getOrder () {
        return $this -> _order;
    }

    public function setResource ($resource = null) {
        if (null === $resource || is_string ($resource) || $resource instanceof Zend_Acl_Resource_Interface) {
            $this -> _resource = $resource;
        } else {
            throw new Jf_Tree_Exception ('Invalid argument: $resource must be null, a string, ' . ' or an instance of Zend_Acl_Resource_Interface');
        }
        return $this;
    }

    public function getResource () {
        return $this -> _resource;
    }

    public function setPrivilege ($privilege = null) {
        $this -> _privilege = is_string ($privilege) ? $privilege : null;
        return $this;
    }

    public function getPrivilege () {
        return $this -> _privilege;
    }

    public function setActive ($active = true) {
        $this -> _active = (bool) $active;
        return $this;
    }

    public function isActive ($recursive = false) {
        if ( ! $this -> _active && $recursive) {
            foreach ($this -> _nodes as $node) {
                if ($node -> isActive (true)) {
                    return true;
                }
            }
            return false;
        }
        return $this -> _active;
    }

    public function getActive ($recursive = false) {
        return $this -> isActive ($recursive);
    }

    public function setVisible ($visible = true) {
        if (is_string ($visible) && 'false' == strtolower ($visible)) {
            $visible = false;
        }
        $this -> _visible = (bool) $visible;
        return $this;
    }

    public function isVisible ($recursive = false) {
        if ($recursive && isset ($this -> _parent) && $this -> _parent instanceof Jf_Tree_Node) {
            if ( ! $this -> _parent -> isVisible (true)) {
                return false;
            }
        }
        return $this -> _visible;
    }

    public function getVisible ($recursive = false) {
        return $this -> isVisible ($recursive);
    }

    public function setParent (Jf_Tree_Container $parent = null) {
        if ($parent === $this) {
            throw new Jf_Tree_Exception ('A node cannot have itself as a parent');
        }
        // return if the given parent already is parent
        if ($parent === $this -> _parent) {
            return $this;
        }
        // remove from old parent
        if (null !== $this -> _parent) {
            $this -> _parent -> removeNode ($this);
        }
        // set new parent
        $this -> _parent = $parent;
        // add to parent if node and not already a child
        if (null !== $this -> _parent &&  ! $this -> _parent -> hasNode ($this, false)) {
            $this -> _parent -> addNode ($this);
        }
        return $this;
    }

    public function getParent () {
        return $this -> _parent;
    }

    public function set ($property, $value) {
        if ( ! is_string ($property) || empty ($property)) {
            // throw new Jf_Tree_Exception('Invalid argument: $property must be a non-empty string');
        }
        $method = 'set' . self::_normalizePropertyName ($property);
        if ($method != 'setOptions' && $method != 'setConfig' && method_exists ($this, $method)) {
            $this -> $method ($value);
        } else {
            $this -> _properties[$property] = $value;
        }
        return $this;
    }

    public function get ($property) {
        if ( ! is_string ($property) || empty ($property)) {
            throw new Jf_Tree_Exception ('Invalid argument: $property must be a non-empty string');
        }
        $method = 'get' . self::_normalizePropertyName ($property);
        if (method_exists ($this, $method)) {
            return $this -> $method ();
        } elseif (isset ($this -> _properties[$property])) {
            return $this -> _properties[$property];
        }
        return null;
    }

    public function __set ($name, $value) {
        $this -> set ($name, $value);
    }

    public function __get ($name) {
        return $this -> get ($name);
    }

    public function __isset ($name) {
        $method = 'get' . self::_normalizePropertyName ($name);
        if (method_exists ($this, $method)) {
            return true;
        }
        return isset ($this -> _properties[$name]);
    }

    public function __unset ($name) {
        $method = 'set' . self::_normalizePropertyName ($name);
        if (method_exists ($this, $method)) {
            throw new Jf_Tree_Exception (sprintf ('Unsetting native property "%s" is not allowed', $name));
        }
        if (isset ($this -> _properties[$name])) {
            unset ($this -> _properties[$name]);
        }
    }

    public function __toString () {
        return $this -> _label;
    }

    public function addRel ($relation, $value) {
        if (is_string ($relation)) {
            $this -> _rel[$relation] = $value;
        }
        return $this;
    }

    public function addRev ($relation, $value) {
        if (is_string ($relation)) {
            $this -> _rev[$relation] = $value;
        }
        return $this;
    }

    public function removeRel ($relation) {
        if (isset ($this -> _rel[$relation])) {
            unset ($this -> _rel[$relation]);
        }
        return $this;
    }

    public function removeRev ($relation) {
        if (isset ($this -> _rev[$relation])) {
            unset ($this -> _rev[$relation]);
        }
        return $this;
    }

    public function getDefinedRel () {
        return array_keys ($this -> _rel);
    }

    public function getDefinedRev () {
        return array_keys ($this -> _rev);
    }

    public function getCustomProperties () {
        return $this -> _properties;
    }

    public final function hashCode () {
        return spl_object_hash ($this);
    }

    public function toArray () {
        return array_merge ($this -> getCustomProperties (), array('label' => $this -> getlabel (), 'fragment' => $this -> getFragment (), 'id' => $this -> getId (), 'class' => $this -> getClass (), 'title' => $this -> getTitle (), 'target' => $this -> getTarget (), 'accesskey' => $this -> getAccesskey (), 'rel' => $this -> getRel (), 'rev' => $this -> getRev (), 'customHtmlAttribs' => $this -> getCustomHtmlAttribs (), 'order' => $this -> getOrder (), 'resource' => $this -> getResource (), 'privilege' => $this -> getPrivilege (), 'active' => $this -> isActive (), 'visible' => $this -> isVisible (), 'type' => get_class ($this), 'nodes' => parent::toArray ()));
    }

    protected static function _normalizePropertyName ($property) {
        return str_replace (' ', '', ucwords (str_replace ('_', ' ', $property)));
    }

    public static function setDefaultNodeType ($type = null) {
        if ($type !== null &&  ! is_string ($type)) {
            throw new Jf_Tree_Exception ('Cannot set default node type: type is no string but should be');
        }
        self::$_defaultNodeType = $type;
    }

    public static function getDefaultNodeType () {
        return self::$_defaultNodeType;
    }

    public function getHref () {}

}
